using System.Collections;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Magiq.Insert;
using Magiq.Support;
using Magiq.Support.Database;
using Magiq.Update;

namespace Magiq.Sql {
    public class MagiqToSqlInsertProvider : IInsertProvider {
        #region IInsertProvider Members

        public int Execute<F, S, T>(IQueryableInsert<F, S, T> insert) where S : class {
            var dataContext = insert.Source.DataContext();
            var dataProvider = new SqlDatabaseAdapter(dataContext);
            var newExpression = (MemberInitExpression) insert.NewExpression.Body;
            var assignments = new List<IPropertyValueAssignment>();
            
            
            foreach (MemberAssignment assignment in newExpression.Bindings) {
                var parameter = assignment.Expression.IsConstant()
                                    ? Expression.Parameter(newExpression.Type, "x")
                                    : assignment.Expression.GetParameter();
                var lambda = Expression.Lambda(assignment.Expression, parameter);
                var p = new InsertPropertyAssignment((PropertyInfo) assignment.Member, lambda);
                assignments.Add(p);
            }

            //inheritance
            var destinationInheritance = new TypeInheritanceMapping(newExpression.Type, dataContext);
            
            if (destinationInheritance.IsInheritance) {
                var discriminatorProperty = destinationInheritance.DiscriminatorProperty;
                var valueExpression = Expression.Lambda(Expression.Constant(destinationInheritance.DiscriminatorCode),
                                                        Expression.Parameter(destinationInheritance.Type, "x"));
                assignments.Add(new InsertPropertyAssignment(discriminatorProperty, valueExpression));
            }

            //collection
            var entitySetAdapter = EntitySetAdapterFrom<S>(insert.Destination);

            if (entitySetAdapter != null) {
                var member = entitySetAdapter.Member;

                for (var i = 0; i < member.Association.ThisKey.Count; i++) {
                    var parentKeyValue = entitySetAdapter.Instance.Property(member.Association.ThisKey[i].Name);
                    var valueExpression = Expression.Lambda(Expression.Constant(parentKeyValue),
                                                            Expression.Parameter(destinationInheritance.Type, "x"));
                    var childKeyProperty = (PropertyInfo) member.Association.OtherKey[i].Member;
                    assignments.Add(new InsertPropertyAssignment(childKeyProperty, valueExpression));
                }
            }

            var queryExecutor = new InsertExecutor(destinationInheritance.Type, dataProvider, insert.Source, assignments);

            var values = queryExecutor.BaseSelectedValues;
            var columns = queryExecutor.Columns;

            var insertSql = "INSERT INTO " + queryExecutor.BaseTableName + " (" + columns.Join(",") +
                            ") SELECT " + values.Join(",") +
                            " FROM " + queryExecutor.BaseWhereClause;

            var value = queryExecutor.ExecuteNonQuery(insertSql);

            return value;
        }

        #endregion

        private static EntitySetAdapter EntitySetAdapterFrom<T>(IEnumerable destination) where T : class {
            if (destination is IEntitySetAdapterProvider)
                return ((IEntitySetAdapterProvider) destination).EntitySetAdapter;

            if (destination is EntitySet<T>)
                return new EntitySetAdapter(destination);

            return null;
        }
    }
}